package be.ipl.diapero.domaine;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;

import javax.persistence.CollectionTable;
import javax.persistence.ElementCollection;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.MapKeyJoinColumn;
import javax.persistence.Table;
import javax.persistence.Transient;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;

import be.ipl.diapero.domaine.Action.Diablotin;
import be.ipl.diapero.domaine.De.Couleur;

@Entity
@Table(schema = "DIAVOLO", name = "TOURS")
@SuppressWarnings("serial")
public class Tour implements Serializable {

	private static final int NB_DES = 9;
	
	public enum Ordre {
		PLUS_GRANDE_SOMME, 
		PLUS_GRAND_ECART, 
		PLUS_HAUTE_VALEUR, 
		PLUS_PETITE_SOMME, 
		PLUS_GRAND_NOMBRE_D_IMPAIR, 
		PRODUIT_PLUS_PROCHE_DE_TRENTE
	}

	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private int id;

	@NotNull
	@Min(1)
	private int numero;

	@ElementCollection
	@CollectionTable(name = "DES", schema = "DIAVOLO", joinColumns = @JoinColumn(name = "TOUR_ID"))
	private List<De> desLances = new ArrayList<De>(9);

	@ElementCollection
	@CollectionTable(name = "ACTIONS", schema = "DIAVOLO", joinColumns = @JoinColumn(name = "TOUR_ID"))
	@MapKeyJoinColumn(name = "JOUEUR_ID")
	private Map<Joueur, Action> actions = new HashMap<Joueur, Action>();

	@Enumerated(EnumType.STRING)
	private Ordre ordre;

	@Transient
	private Random random = new Random();

	protected Tour() {
		super();
	}
	
	public Tour(int numero) {
		this.numero = numero;
	}

	public int getId() {
		return id;
	}

	public int getNumero() {
		return numero;
	}

	public List<De> getDesLances() {
		return desLances;
	}

	public Map<Joueur, Action> getActions() {
		return actions;
	}

	public Ordre getOrdre() {
		return ordre;
	}

	public void lancerDes() {
		Ordre[] tabOrdres = Ordre.values();
		this.ordre = tabOrdres[this.random.nextInt(6)];
		
		Couleur couleurDe= Couleur.BLANC;

		for (int i = 0; i < NB_DES; i++) {
			if (i >= 3)
				couleurDe = Couleur.ROUGE;
			if (i >= 6)
				couleurDe = Couleur.NOIR;

			this.desLances.add(new De(this.random.nextInt(6) + 1,
					couleurDe));
		}
		Collections.shuffle(this.desLances);
	}
	
	private Diablotin couleurGagnante() {
		List<De> desBlancs = extraireDesParCouleur(Couleur.BLANC);
		List<De> desRouges = extraireDesParCouleur(Couleur.ROUGE);
		List<De> desNoirs = extraireDesParCouleur(Couleur.NOIR);
		Diablotin diablotinGagnant;
		
		switch (this.ordre) {

			case PLUS_GRANDE_SOMME:
				diablotinGagnant = gagnantPlusGrandeSomme(desBlancs, desRouges, desNoirs);
				break;
			case PLUS_PETITE_SOMME:
				diablotinGagnant = gagnantPlusPetiteSomme(desBlancs, desRouges, desNoirs);
				break;
			case PLUS_GRAND_ECART:
				diablotinGagnant = gagnantPlusGrandEcart(desBlancs, desRouges, desNoirs);
				break;
			case PLUS_GRAND_NOMBRE_D_IMPAIR:
				diablotinGagnant = gagnantPlusGrandNombreDImpair(desBlancs, desRouges,
						desNoirs);
				break;
			case PLUS_HAUTE_VALEUR:
				diablotinGagnant = gagnantPlusHauteValeur(desBlancs, desRouges, desNoirs);
				break;
			case PRODUIT_PLUS_PROCHE_DE_TRENTE:
				diablotinGagnant = gagnantProduitProcheDeTrente(desBlancs, desRouges, desNoirs);
				break;

			default:
				diablotinGagnant = Diablotin.GRIS; // Dans aucun cas...
				break;
		}

		return diablotinGagnant;
	}
	
	private List<De> extraireDesParCouleur(Couleur couleur) {
		List<De> liste = new ArrayList<De>(3);
		for (De de : this.desLances) {
			if (de.getCouleur() == couleur)
				liste.add(de);
		}
		return liste;
	}
	
	private Diablotin gagnantPlusGrandeSomme(List<De> desBlancs,
			List<De> desRouges, List<De> desNoirs) {
		int max = 0;
		Diablotin diablotinGagnant = null;

		int sommeBlancs = 0, sommeRouges = 0, sommeNoirs = 0;

		sommeBlancs = extraireSomme(desBlancs);
		sommeRouges = extraireSomme(desRouges);
		sommeNoirs = extraireSomme(desNoirs);
		
		if (sommeBlancs > max) {
			max = sommeBlancs;
			diablotinGagnant = Diablotin.BLANC;
		}

		if (sommeRouges > max) {
			max = sommeRouges;
			diablotinGagnant = Diablotin.ROUGE;
		} else if (sommeRouges == max) {
			diablotinGagnant = Diablotin.GRIS;
		}

		if (sommeNoirs > max) {
			max = sommeNoirs;
			diablotinGagnant = Diablotin.NOIR;
		} else if (sommeNoirs == max) {
			diablotinGagnant = Diablotin.GRIS;
		}
		return diablotinGagnant;
	}
	
	private int extraireSomme(List<De> listeDes){
		int somme = 0;
		for (De de : listeDes) {
			 somme += de.getValeur();
		}
		return somme;
	}
	
	private Diablotin gagnantPlusPetiteSomme(List<De> desBlancs,
			List<De> desRouges, List<De> desNoirs) {
		int min = 100;
		Diablotin diablotinGagnant = null;
		int sommeBlancs = 0, sommeRouges = 0, sommeNoirs = 0;

		sommeBlancs = extraireSomme(desBlancs);
		sommeRouges = extraireSomme(desRouges);
		sommeNoirs = extraireSomme(desNoirs);

		if (sommeBlancs < min) {
			min = sommeBlancs;
			diablotinGagnant = Diablotin.BLANC;
		} 

		if (sommeRouges < min) {
			min = sommeRouges;
			diablotinGagnant = Diablotin.ROUGE;
		} else if (sommeRouges == min) {
			diablotinGagnant = Diablotin.GRIS;
		}

		if (sommeNoirs < min) {
			min = sommeNoirs;
			diablotinGagnant = Diablotin.NOIR;
		} else if (sommeNoirs == min) {
			diablotinGagnant = Diablotin.GRIS;
		}

		return diablotinGagnant;
	}

	private Diablotin gagnantPlusGrandEcart(List<De> desBlancs,
			List<De> desRouges, List<De> desNoirs) {
		int plusGrandEcart = -1;
		Diablotin diablotinGagnant = null;
		
		int maxBlanc = extraireLeMax(desBlancs);
		int minBlanc = extraireLeMin(desBlancs);
		int maxRouge = extraireLeMax(desRouges);
		int minRouge = extraireLeMin(desRouges);
		int maxNoir = extraireLeMax(desNoirs);
		int minNoir = extraireLeMin(desNoirs);
		int ecartBlanc = maxBlanc - minBlanc;
		int ecartRouge = maxRouge - minRouge;		
		int ecartNoir = maxNoir - minNoir;
		
		if (ecartBlanc > plusGrandEcart)
			diablotinGagnant = Diablotin.BLANC;

		if (ecartRouge > plusGrandEcart)
			diablotinGagnant = Diablotin.ROUGE;
		else if (ecartRouge == plusGrandEcart){
			diablotinGagnant = Diablotin.GRIS;
		}

		if (ecartNoir > plusGrandEcart)
			diablotinGagnant = Diablotin.NOIR;
		else if (ecartNoir == plusGrandEcart){
			diablotinGagnant = Diablotin.GRIS;
		}
		return diablotinGagnant;
	}
	
	private int extraireLeMax(List<De> listeDes){
		int max = 0;
		for (De de : listeDes) {
			 if(de.getValeur()>max) max = de.getValeur();
		}
		return max;
	}
	
	private int extraireLeMin(List<De> listeDes){
		int min = 7;
		for (De de : listeDes) {
			 if(de.getValeur()<min) min = de.getValeur();
		}
		return min;
	}

	private Diablotin gagnantPlusGrandNombreDImpair(List<De> desBlancs,
			List<De> desRouges, List<De> desNoirs) {
		int maxNbImpair = 0;
		Diablotin diablotinGagnant = null;

		int nbImpairBlanc = extraireNbrDImpair(desBlancs);
		int nbImpairRouge = extraireNbrDImpair(desRouges); 
		int	nbImpairNoir = extraireNbrDImpair(desNoirs);

		if (nbImpairBlanc > maxNbImpair) {
			maxNbImpair = nbImpairBlanc;
			diablotinGagnant = Diablotin.BLANC;
		} else if (nbImpairBlanc == maxNbImpair) {
			diablotinGagnant = Diablotin.GRIS;
		}

		if (nbImpairRouge > maxNbImpair) {
			maxNbImpair = nbImpairRouge;
			diablotinGagnant = Diablotin.ROUGE;
		} else if (nbImpairRouge == maxNbImpair) {
			diablotinGagnant = Diablotin.GRIS;
		}

		if (nbImpairNoir > maxNbImpair) {
			maxNbImpair = nbImpairNoir;
			diablotinGagnant = Diablotin.NOIR;
		} else if (nbImpairNoir == maxNbImpair) {
			diablotinGagnant = Diablotin.GRIS;
		}
		return diablotinGagnant;
	}
	
	private int extraireNbrDImpair(List<De> listeDes){
		int nbrDImpair = 0;
		for (De de : listeDes) {
			if (de.getValeur() % 2 == 1)
				nbrDImpair++;
		}
		return nbrDImpair;
	}
	
	private Diablotin gagnantPlusHauteValeur(List<De> desBlancs,
			List<De> desRouges, List<De> desNoirs) {
		int max = 0;
		Diablotin diablotinGagnant = null;

		int maxBlanc = extrairePlusHauteValeur(desBlancs);
		int maxRouge = extrairePlusHauteValeur(desRouges);
		int maxNoir =  extrairePlusHauteValeur(desNoirs);

		if (maxBlanc > max) {
			max = maxBlanc;
			diablotinGagnant = Diablotin.BLANC;
		} 

		if (maxRouge > max) {
			max = maxRouge;
			diablotinGagnant = Diablotin.ROUGE;
		} else if (maxRouge == max) {
			diablotinGagnant = Diablotin.GRIS;
		}

		if (maxNoir > max) {
			max = maxNoir;
			diablotinGagnant = Diablotin.NOIR;
		} else if (maxNoir == max) {
			diablotinGagnant = Diablotin.GRIS;
		}

		return diablotinGagnant;
	}

	private int extrairePlusHauteValeur(List<De> listeDes){
		int max = 0;
		for (De de : listeDes) {
			if (de.getValeur() > max)
				max = de.getValeur();
		}
		return max;
	}
	
	private Diablotin gagnantProduitProcheDeTrente(List<De> desBlancs,
			List<De> desRouges, List<De> desNoirs) {
		Diablotin diablotinGagnant = null;

		int produitBlanc = extraireProduit(desBlancs);
		int produitRouge = extraireProduit(desRouges);
		int produitNoir =  extraireProduit(desNoirs);

		int diffProduitBlanc = Math.abs(produitBlanc - 30);
		int diffProduitRouge = Math.abs(produitRouge - 30);
		int diffProduitNoir = Math.abs(produitNoir - 30);
		
		if(diffProduitBlanc == diffProduitNoir || 
			diffProduitNoir == diffProduitRouge || 
			diffProduitBlanc == diffProduitRouge){
			return Diablotin.GRIS;
		}
		
		int min = Math.min(diffProduitBlanc, diffProduitNoir);
		min = Math.min(diffProduitRouge, min);
		
		if (diffProduitBlanc == min) {
			diablotinGagnant = Diablotin.BLANC;
		} 

		if (diffProduitRouge == min) {
			diablotinGagnant = Diablotin.ROUGE;
		}
		
		if (diffProduitNoir == min) {
			diablotinGagnant = Diablotin.NOIR;
		}
		return diablotinGagnant;
	}

	private int extraireProduit(List<De> listeDes){
		int produit = 1;
		for(De de : listeDes){
			produit *= de.getValeur();
		}
		return produit;
	}
	
	public boolean effectuerAction(Partie partie, Joueur joueur, Action action) {
		if (this.actions.containsKey(joueur))
			return false;
		this.actions.put(joueur, action);
		Diablotin couleurGagnante = couleurGagnante();

		if (couleurGagnante==Diablotin.GRIS	&& action.getDiablotin()==Diablotin.GRIS) {
			if (action.getNbrDeGemmes() < Partie.NBR_GEMMES_INITIAL) {
				Action actionGagnante = new Action(action.getNbrDeGemmes() + 1,
						action.getDiablotin());
				this.actions.put(joueur, actionGagnante);
				partie.augmenterGemmes(joueur);
			}
		}else if (action.getDiablotin() != couleurGagnante || action.getDiablotin() == Diablotin.RIEN) {
			if (action.getNbrDeGemmes() > 0) {
				Action actionPerdante = new Action(action.getNbrDeGemmes() - 1,
						action.getDiablotin());
				this.actions.put(joueur,actionPerdante);
				partie.diminuerGemmes(joueur);
			}
		}
		return true;
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + id;
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Tour other = (Tour) obj;
		if (id != other.id)
			return false;
		return true;
	}
}
